#!/bin/sh
#

#
# Script to deploy the boot files from rootfs to the boot partition
#
# This hook is meant to run in the `next` resinOS container
#
# Will skip files that are either blacklisted, or are flagged by the do_skip()
# function. The latter is typically defined in the `os-helpers-sb` script
# used in secure boot enabled systems to discriminate the destination boot
# partition to install into.

set -o errexit

# shellcheck disable=SC1091
. /usr/sbin/balena-config-vars
# shellcheck disable=SC1091
. /usr/libexec/os-helpers-logging
# shellcheck disable=SC1091
[ -f /usr/libexec/os-helpers-sb ] && . /usr/libexec/os-helpers-sb
# shellcheck disable=SC1091
. /usr/sbin/balena-config-defaults
# shellcheck disable=SC1091
command -v generate_bootpart_fingerprint > /dev/null || . /usr/libexec/os-helpers-fs

# Define the boot partition mountpoint depending on the calling script name
# On secure boot systems that split the boot partition the script is called with different names
boot_mountpoint="${BALENA_BOOT_MOUNTPOINT}"
[ $(basename "$0") != "1-bootfiles" ] && boot_mountpoint="${BALENA_NONENC_BOOT_MOUNTPOINT}"

# Variables
boot_fingerprint="@BALENA_BOOT_FINGERPRINT@"
bootfiles_blacklist="@BALENA_BOOTFILES_BLACKLIST@"
DURING_UPDATE=${DURING_UPDATE:-0}

# Checks if a file is present in the blacklist
# Arguments:
# 	$1: file to be checked
# Return value:
# 	0: file is blacklisted
# 	1: file is not blacklisted
isBlacklisted() {
	local _file="$1"
	for b in $bootfiles_blacklist; do
		if [ "$b" = "$_file" ]; then
			return 0
		fi
	done
	return 1
}

# Checks if a file was modified by verifying its fingerprint
# Arguments:
# 	$1: file to be checked
# Return value:
# 	0: file is modified
# 	1: file is not modified
isModified() {
	local _file="$1"
	local _current_md5
	_current_md5=$("${CAT}" "$boot_mountpoint/$_file" | md5sum | awk '{print $1}')
	local _initial_md5
	# Boot partition fingerprint use relative paths
	_initial_md5=$("${CAT}" "$boot_mountpoint/$boot_fingerprint" | grep "$(echo "${_file}" | sed 's/^\///')" | awk '{print $1}')
	if [ "$_current_md5" != "$_initial_md5" ]; then
		return 0
	else
		return 1
	fi
}

# Copies a file from /resin-boot to boot partition filesystem atomically and durable
# Arguments:
# 	$1: boot partition file
copyBootFile() {
	local _file="$1"
	mkdir -p "$boot_mountpoint/$(dirname "$_file")"
	if ! "${UCP}" "/resin-boot/$_file" "$boot_mountpoint/$_file"; then
		if [ "$DURING_UPDATE" = "1" ]; then
			# Cleanup all new files we deployed
			for file in $new_deployed_files; do
				rm -f "$boot_mountpoint/$file"
			done
			exit 1
		fi
	fi
}

# Deploys files to boot partition
# Arguments:
# 	$1: file path relative to boot partition's root
deploy() {
	local _file="$1"
	if type do_skip >/dev/null 2>&1 && do_skip "$_file"; then
		return
	fi
	info "Deploying ${boot_mountpoint}${_file}"
	if isBlacklisted "$_file"; then
		if [ "$_file" = "/splash/balena-logo.png" ]; then
			if [ -f "$boot_mountpoint/splash/resin-logo.png" ]; then
				if isModified "/splash/resin-logo.png"; then
					# Keep custom logo
					info "renaming resin-logo to balena-logo..."
					sync -f "$boot_mountpoint"
					"${MV}" "$boot_mountpoint/splash/resin-logo.png" "$boot_mountpoint/splash/balena-logo.png"
					sync -f "$boot_mountpoint"
				else
					# This rebrands from old resin logo
					info "replacing resin-logo with balena-logo..."
					copyBootFile "$_file"
					rm "$boot_mountpoint/splash/resin-logo.png"
				fi
				info "Deployed ${boot_mountpoint}${_file}.\n"
				return
			fi
		elif [ "$_file" = "/config.json" ]; then
			development_mode="$("${CAT}" "${boot_mountpoint}$_file" | jq -r ".developmentMode")"
			if [ -z "${development_mode}" ] || [ "${development_mode}" = "null" ]; then
				# Only configure developmentMode if updating from a legacy development image
				old_os_os_release=$(find "/mnt/sysroot/active" -path "*/etc/os-release")
				if grep -i -q 'VARIANT="Development"' "${old_os_os_release}"; then
					"${CAT}" "${boot_mountpoint}/config.json" | jq -S ".developmentMode=true" | "${WR}" "${boot_mountpoint}/config.json"
					info "Development mode set in new OS... "
				fi
			fi
		fi
		info "${boot_mountpoint}${_file} blacklisted. Ignoring."
	else
		if [ -f "$boot_mountpoint/$_file" ]; then
			if isModified "$_file"; then
				info " overwriting modified file ${boot_mountpoint}${_file}..."
				copyBootFile "$_file"
			else
				copyBootFile "$_file"
			fi
		else
			new_deployed_files="$new_deployed_files $_file"
			info " new file ${boot_mountpoint}${_file}..."
			copyBootFile "$_file"
		fi
	fi
}


#
# MAIN
#

# Do a dry run for copying the boot files and figure out if we would get in an
# out of space situation
boot_space="$(df -B1 --output=avail "$boot_mountpoint" | grep -v Avail)"
available="$boot_space"
available_threshold="524288" # All sizes in bytes
for filepath in $(find /resin-boot -type f | sed 's#^/resin-boot##g'); do
	if type do_skip >/dev/null 2>&1 && do_skip "$filepath"; then
		continue
	fi
	if isBlacklisted "$filepath"; then
		continue
	fi
	filesize=$(stat --format %s "/resin-boot${filepath}")
	available="$((available - filesize))"
	if [ "$available" -lt "$available_threshold" ]; then
		error "Boot files copy operations will fail with out of space error."
		if [ "$DURING_UPDATE" = "1" ]; then
			exit 1
		fi
	fi
	if [ -f "${boot_mountpoint}${filepath}" ]; then
		available="$((available + $(stat --format %s "${boot_mountpoint}${filepath}")))"
	fi
done
info "Boot partition can accomodate the new update."

find -L "${boot_mountpoint}" $(printf "! -name %s " $(for blacklisted_file in  $bootfiles_blacklist; do echo $blacklisted_file | awk -F'/' '{print $NF}'; done)) -exec touch {} +
info "Updated timestamps for all files in ${boot_mountpoint}"

# Deploy all files in the bootfiles list except fingerprint
new_deployed_files=""
for filepath in $(find /resin-boot -type f | sed 's#^/resin-boot##g'); do
	if [ "$filepath" != "/$boot_fingerprint" ]; then
		deploy "$filepath"
	fi
done

generate_bootpart_fingerprint "${boot_mountpoint}"
